package com.jvm.simulation.hotspot;

import cn.hutool.core.io.FileUtil;
import com.jvm.simulation.hotspot.attributes.AttributeInfo;
import com.jvm.simulation.hotspot.attributes.BootstrapMethods;
import com.jvm.simulation.hotspot.attributes.InnerClasses;
import lombok.var;
import org.apache.commons.lang3.ObjectUtils;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * ClassFile {
 *     u4             magic; //Class 文件的标志
 *     u2             minor_version;//Class 的小版本号
 *     u2             major_version;//Class 的大版本号
 *     u2             constant_pool_count;//常量池的数量
 *     cp_info        constant_pool[constant_pool_count-1];//常量池
 *     u2             access_flags;//Class 的访问标记
 *     u2             this_class;//当前类
 *     u2             super_class;//父类
 *     u2             interfaces_count;//接口
 *     u2             interfaces[interfaces_count];//一个类可以实现多个接口
 *     u2             fields_count;//Class 文件的字段属性
 *     field_info     fields[fields_count];//一个类会可以有个字段
 *     u2             methods_count;//Class 文件的方法数量
 *     method_info    methods[methods_count];//一个类可以有个多个方法
 *     u2             attributes_count;//此类的属性表中的属性数
 *     attribute_info attributes[attributes_count];//属性表集合
 * }
 */
public class LoadClassFile {

    static byte[] u4Bytes = new byte[Constants.u4];
    static byte[] u2Bytes = new byte[Constants.u2];
    static byte[] u1Bytes = new byte[Constants.u1];

    /**
     *  用于存储该类加载器加载的所有类
     */
    private static Map<String, InstanceKlass> classLoaderData = new HashMap<>();
    /**
     *  main函数所在的类在此保存一份引用，方便快速定位到
     */
    private static InstanceKlass mainKlass = null;

    public static void setMainKlass(InstanceKlass klass) {
        mainKlass = klass;
    }

    public static InstanceKlass findLoadedKlass(String key) {
        return  classLoaderData.computeIfAbsent(key + ".class",(i)->{
            try {
                return loadClassFile(i);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });
    }


    public static InstanceKlass loadClassFile(String filePath) throws Exception {

        InstanceKlass instanceKlass = new InstanceKlass();

        byte[] content = FileUtil.readBytes(new File("D:\\zy-e\\vandearm\\ziya-jvm-teach\\target\\classes\\" + filePath));

        int idx = 0;
        System.arraycopy(content,idx,instanceKlass.getMagic(),0, Constants.u4);
        idx += Constants.u4;
        System.arraycopy(content,idx,instanceKlass.getMinor_version(),0, Constants.u2);
        idx += Constants.u2;
        System.arraycopy(content,idx,instanceKlass.getMajor_version(),0, Constants.u2);
        idx += Constants.u2;
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        int cnt;
        instanceKlass.setConstant_pool_count(cnt = ByteUtil.byteToUnsignedShort(u2Bytes));

        idx = instanceKlass.setConstant_pool(content,cnt, Constants.u2,idx);

        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        instanceKlass.setAccess_flags(ByteUtil.byteToUnsignedShort(u2Bytes));
        idx += Constants.u2;
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        instanceKlass.setThis_class(ByteUtil.byteToUnsignedShort(u2Bytes));
        idx += Constants.u2;
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        instanceKlass.setSuper_class(ByteUtil.byteToUnsignedShort(u2Bytes));
        idx += Constants.u2;
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        instanceKlass.setInterfaces_count(cnt = ByteUtil.byteToUnsignedShort(u2Bytes));

        idx += Constants.u2;
        idx = instanceKlass.setInterfaces(content,cnt, Constants.u2,idx);

        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        instanceKlass.setFields_count(cnt = ByteUtil.byteToUnsignedShort(u2Bytes));
        idx += Constants.u2;
        idx = instanceKlass.setField_info(content,cnt, Constants.u2,idx);

        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);

        instanceKlass.setMethods_count(cnt = ByteUtil.byteToUnsignedShort(u2Bytes));
        idx += Constants.u2;
        idx = instanceKlass.setMethod_info(content,cnt, Constants.u2,idx);
        // 属性数量
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        instanceKlass.setAttributes_count(ByteUtil.byteToUnsignedShort(u2Bytes));
        // 属性
        for (int i = 0; i < instanceKlass.getAttributes_count(); i++) {
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
//            idx += Constants.u2;
            String attrName = (String) instanceKlass.getContantPool().getContantPool().get(ByteUtil.byteToUnsignedShort(u2Bytes));
            if (attrName.equals("SourceFile")) {
                idx = parseSourceFile(content, idx, instanceKlass,i);
            } else if(attrName.equals("InnerClasses")){
                idx = innerClassesInfo(content, idx, instanceKlass,i);
            }else{
                idx = bootstrapMethodsInfo(content, idx, instanceKlass,i);
            }
        }

        return instanceKlass;

    }


    private static int innerClassesInfo(byte[] content, int idx, InstanceKlass instanceKlass, int i) {
        InnerClasses attributeInfo = new InnerClasses();
        instanceKlass.getAttribute_info()[i] = (attributeInfo);
        // name index
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        attributeInfo.setAttrNameIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

        // length
        System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
        idx += Constants.u4;

        attributeInfo.setAttrLength(ByteUtil.byteArrayToInt(u4Bytes));

        attributeInfo.initContainer();

        InnerClasses.Item item = attributeInfo.new Item();
        //innerClassIndx
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        attributeInfo.setTableLength(ByteUtil.byteToUnsignedShort(u2Bytes));
        attributeInfo.initTable();
        for (int i1 = 0; i1 < attributeInfo.getTableLength(); i1++) {
            attributeInfo.getTable()[i1] = item;
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            item.setInnerClassIndx(ByteUtil.byteToUnsignedShort(u2Bytes));
            //outerClassIdx
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            item.setOuterClassIdx(ByteUtil.byteToUnsignedShort(u2Bytes));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            item.setInnerNameIdx(ByteUtil.byteToUnsignedShort(u2Bytes));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            item.setAccessFlags(new AccessFlags(ByteUtil.byteToUnsignedShort(u2Bytes)));
        }

        return idx;
    }

    private static int bootstrapMethodsInfo(byte[] content, int idx, InstanceKlass instanceKlass, int i) {
        BootstrapMethods attributeInfo = new BootstrapMethods();
        instanceKlass.getAttribute_info()[i] = (attributeInfo);
        // name index
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        attributeInfo.setAttrNameIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

        // length
        System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
        idx += Constants.u4;

        attributeInfo.setAttrLength(ByteUtil.byteArrayToInt(u4Bytes));

        attributeInfo.initContainer();

        BootstrapMethods.Item item = attributeInfo.new Item();
        //innerClassIndx
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        attributeInfo.setTableLength(ByteUtil.byteToUnsignedShort(u2Bytes));
        attributeInfo.initTable();
        for (int i1 = 0; i1 < attributeInfo.getTableLength(); i1++) {
            attributeInfo.getTable()[i1] = item;
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            item.setBootstrapMethodIdx(ByteUtil.byteToUnsignedShort(u2Bytes));
            //outerClassIdx
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;

            int argumentLength;
            item.setArgumentLength(argumentLength = ByteUtil.byteToUnsignedShort(u2Bytes));
            item.initArgument();
            for (int i2 = 0; i2 < argumentLength; i2++) {
                BootstrapMethods.Item.Argument argument = item.new Argument();
                item.getArguments()[i2] = argument;
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;
                argument.setArgumentIdx(ByteUtil.byteToUnsignedShort(u2Bytes));
            }
        }
        return idx;
    }


    private static int parseSourceFile(byte[] content, int idx, InstanceKlass instanceKlass, int i) {
        byte[] u2Arr = new byte[2];
        byte[] u4Arr = new byte[4];

        AttributeInfo attributeInfo = new AttributeInfo();

        instanceKlass.getAttribute_info()[i] = (attributeInfo);

        // name index
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        attributeInfo.setAttrNameIndex(ByteUtil.byteToUnsignedShort(u2Arr));

        // length
        System.arraycopy(content,idx,u4Arr,0, Constants.u4);
        idx += Constants.u4;

        attributeInfo.setAttrLength(ByteUtil.byteArrayToInt(u4Arr));

        attributeInfo.initContainer();

        // data
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        return idx;

    }

    public static int getInt(byte[] bytes) {
        int high = bytes[0];
        int low = bytes[1];
        return (high << 8 & 0xFF00) | (low & 0xFF);
    }
    public static void main(String[] args) throws Exception {
        String classFileName = "com\\ziya\\jvm\\example\\HelloWorld".replace("\\","/") ;

        InstanceKlass instanceKlass = findLoadedKlass(classFileName);

        runMethod(instanceKlass,"main","([Ljava/lang/String;)V");
    }

    public static void runMethod(InstanceKlass instanceKlass,String methodName,String descriptorName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MethodInfo mainMethod ;
        for (MethodInfo methodInfo : instanceKlass.getMethod_info()) {

            if(methodName.equals(methodInfo.getMethodName()) && methodInfo.getDescriptor().equals(descriptorName)){

                JavaNativeInterface.callStaticMethod(methodInfo);
                break;
            }
        }
    }

}
